package net.qiqbframework.commandhandling.gateway;


import net.qiqbframework.commandhandling.CommandBus;
import net.qiqbframework.commandhandling.CommandCallback;
import net.qiqbframework.commandhandling.CommandMessage;
import net.qiqbframework.common.Registration;
import net.qiqbframework.messaging.intercepting.MessageDispatchInterceptor;

import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import static java.util.Arrays.asList;
import static net.qiqbframework.commandhandling.GenericCommandMessage.of;
import static net.qiqbframework.common.BuilderUtils.assertNonNull;

public abstract class AbstractCommandGateway {

    private final CommandBus commandBus;
    private final RetryScheduler retryScheduler;
    private final List<MessageDispatchInterceptor<? super CommandMessage<?>>> dispatchInterceptors;

    protected AbstractCommandGateway(Builder builder) {
        builder.validate();
        this.commandBus = builder.commandBus;
        this.retryScheduler = builder.retryScheduler;
        this.dispatchInterceptors = builder.dispatchInterceptors;
    }

    protected <C, R> void send(@Nonnull C command, @Nonnull CommandCallback<? super C, ? super R> callback) {
        CommandMessage<? extends C> commandMessage = processInterceptors(of(command));
        CommandCallback<? super C, ? super R> commandCallback = callback;
        if (retryScheduler != null) {
            commandCallback = new RetryingCallback<>(callback, retryScheduler, commandBus);
        }
        commandBus.dispatch(commandMessage, commandCallback);
    }

    protected <C, R> void batchSend(@Nonnull List<?> command ) {
        List<CommandMessage<?>> commandMessageList = new ArrayList<>();
        for (Object o : command) {
            commandMessageList.add(processInterceptors(of(o)));
        }

        commandBus.batchDispatch(commandMessageList);

    }


    protected Registration registerDispatchInterceptor(
            @Nonnull MessageDispatchInterceptor<? super CommandMessage<?>> interceptor) {
        dispatchInterceptors.add(interceptor);
        return () -> dispatchInterceptors.remove(interceptor);
    }


    @SuppressWarnings("unchecked")
    protected <C> CommandMessage<? extends C> processInterceptors(CommandMessage<C> commandMessage) {
        CommandMessage<? extends C> message = commandMessage;

        for (MessageDispatchInterceptor<? super CommandMessage<?>> dispatchInterceptor : dispatchInterceptors) {
            message = (CommandMessage) dispatchInterceptor.handle(message);
        }
        return message;
    }


    public CommandBus getCommandBus() {
        return commandBus;
    }

    public abstract static class Builder {

        private CommandBus commandBus;
        private RetryScheduler retryScheduler;
        private List<MessageDispatchInterceptor<? super CommandMessage<?>>> dispatchInterceptors =
                new CopyOnWriteArrayList<>();


        public Builder commandBus(@Nonnull CommandBus commandBus) {
            assertNonNull(commandBus, "CommandBus may not be null");
            this.commandBus = commandBus;
            return this;
        }


        public Builder retryScheduler(@Nonnull RetryScheduler retryScheduler) {
            this.retryScheduler = retryScheduler;
            return this;
        }

        public Builder dispatchInterceptors(
                MessageDispatchInterceptor<? super CommandMessage<?>>... dispatchInterceptors) {
            return dispatchInterceptors(asList(dispatchInterceptors));
        }


        public Builder dispatchInterceptors(
                List<MessageDispatchInterceptor<? super CommandMessage<?>>> dispatchInterceptors) {
            this.dispatchInterceptors = dispatchInterceptors != null && !dispatchInterceptors.isEmpty()
                    ? new CopyOnWriteArrayList<>(dispatchInterceptors)
                    : new CopyOnWriteArrayList<>();
            return this;
        }

        protected void validate() {
            assertNonNull(commandBus, "The CommandBus is a hard requirement and should be provided");
        }
    }
}
